/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var zrUtil = require("static/plugins/js/zrender/lib/core/util");

var RoamController = require("./RoamController");

var roamHelper = require("../../component/helper/roamHelper");

var _cursorHelper = require("../../component/helper/cursorHelper");

var onIrrelevantElement = _cursorHelper.onIrrelevantElement;

var graphic = require("../../util/graphic");

var geoSourceManager = require("../../coord/geo/geoSourceManager");

var _component = require("../../util/component");

var getUID = _component.getUID;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
function getFixedItemStyle(model) {
    var itemStyle = model.getItemStyle();
    var areaColor = model.get("areaColor"); // If user want the color not to be changed when hover,
    // they should both set areaColor and color to be null.

    if (areaColor != null) {
        itemStyle.fill = areaColor;
    }

    return itemStyle;
}

function updateMapSelectHandler(
    mapDraw,
    mapOrGeoModel,
    regionsGroup,
    api,
    fromView
) {
    regionsGroup.off("click");
    regionsGroup.off("mousedown");

    if (mapOrGeoModel.get("selectedMode")) {
        regionsGroup.on("mousedown", function () {
            mapDraw._mouseDownFlag = true;
        });
        regionsGroup.on("click", function (e) {
            if (!mapDraw._mouseDownFlag) {
                return;
            }

            mapDraw._mouseDownFlag = false;
            var el = e.target;

            while (!el.__regions) {
                el = el.parent;
            }

            if (!el) {
                return;
            }

            var action = {
                type:
                    (mapOrGeoModel.mainType === "geo" ? "geo" : "map") +
                    "ToggleSelect",
                batch: zrUtil.map(el.__regions, function (region) {
                    return {
                        name: region.name,
                        from: fromView.uid,
                    };
                }),
            };
            action[mapOrGeoModel.mainType + "Id"] = mapOrGeoModel.id;
            api.dispatchAction(action);
            updateMapSelected(mapOrGeoModel, regionsGroup);
        });
    }
}

function updateMapSelected(mapOrGeoModel, regionsGroup) {
    // FIXME
    regionsGroup.eachChild(function (otherRegionEl) {
        zrUtil.each(otherRegionEl.__regions, function (region) {
            otherRegionEl.trigger(
                mapOrGeoModel.isSelected(region.name) ? "emphasis" : "normal"
            );
        });
    });
}
/**
 * @alias module:echarts/component/helper/MapDraw
 * @param {module:echarts/ExtensionAPI} api
 * @param {boolean} updateGroup
 */

function MapDraw(api, updateGroup) {
    var group = new graphic.Group();
    /**
     * @type {string}
     * @private
     */

    this.uid = getUID("ec_map_draw");
    /**
     * @type {module:echarts/component/helper/RoamController}
     * @private
     */

    this._controller = new RoamController(api.getZr());
    /**
     * @type {Object} {target, zoom, zoomLimit}
     * @private
     */

    this._controllerHost = {
        target: updateGroup ? group : null,
    };
    /**
     * @type {module:zrender/container/Group}
     * @readOnly
     */

    this.group = group;
    /**
     * @type {boolean}
     * @private
     */

    this._updateGroup = updateGroup;
    /**
     * This flag is used to make sure that only one among
     * `pan`, `zoom`, `click` can occurs, otherwise 'selected'
     * action may be triggered when `pan`, which is unexpected.
     * @type {booelan}
     */

    this._mouseDownFlag;
    /**
     * @type {string}
     */

    this._mapName;
    /**
     * @type {boolean}
     */

    this._initialized;
    /**
     * @type {module:zrender/container/Group}
     */

    group.add((this._regionsGroup = new graphic.Group()));
    /**
     * @type {module:zrender/container/Group}
     */

    group.add((this._backgroundGroup = new graphic.Group()));
}

MapDraw.prototype = {
    constructor: MapDraw,
    draw: function (mapOrGeoModel, ecModel, api, fromView, payload) {
        var isGeo = mapOrGeoModel.mainType === "geo"; // Map series has data. GEO model that controlled by map series
        // will be assigned with map data. Other GEO model has no data.

        var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
        isGeo &&
            ecModel.eachComponent(
                {
                    mainType: "series",
                    subType: "map",
                },
                function (mapSeries) {
                    if (
                        !data &&
                        mapSeries.getHostGeoModel() === mapOrGeoModel
                    ) {
                        data = mapSeries.getData();
                    }
                }
            );
        var geo = mapOrGeoModel.coordinateSystem;

        this._updateBackground(geo);

        var regionsGroup = this._regionsGroup;
        var group = this.group;
        var transformInfo = geo.getTransformInfo();
        group.transform = transformInfo.roamTransform;
        group.decomposeTransform();
        group.dirty();
        var scale = transformInfo.rawScale;
        var position = transformInfo.rawPosition;
        regionsGroup.removeAll();
        var itemStyleAccessPath = ["itemStyle"];
        var hoverItemStyleAccessPath = ["emphasis", "itemStyle"];
        var labelAccessPath = ["label"];
        var hoverLabelAccessPath = ["emphasis", "label"];
        var nameMap = zrUtil.createHashMap();
        zrUtil.each(geo.regions, function (region) {
            // Consider in GeoJson properties.name may be duplicated, for example,
            // there is multiple region named "United Kindom" or "France" (so many
            // colonies). And it is not appropriate to merge them in geo, which
            // will make them share the same label and bring trouble in label
            // location calculation.
            var regionGroup =
                nameMap.get(region.name) ||
                nameMap.set(region.name, new graphic.Group());
            var compoundPath = new graphic.CompoundPath({
                segmentIgnoreThreshold: 1,
                shape: {
                    paths: [],
                },
            });
            regionGroup.add(compoundPath);
            var regionModel =
                mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel;
            var itemStyleModel = regionModel.getModel(itemStyleAccessPath);
            var hoverItemStyleModel = regionModel.getModel(
                hoverItemStyleAccessPath
            );
            var itemStyle = getFixedItemStyle(itemStyleModel);
            var hoverItemStyle = getFixedItemStyle(hoverItemStyleModel);
            var labelModel = regionModel.getModel(labelAccessPath);
            var hoverLabelModel = regionModel.getModel(hoverLabelAccessPath);
            var dataIdx; // Use the itemStyle in data if has data

            if (data) {
                dataIdx = data.indexOfName(region.name); // Only visual color of each item will be used. It can be encoded by dataRange
                // But visual color of series is used in symbol drawing
                //
                // Visual color for each series is for the symbol draw

                var visualColor = data.getItemVisual(dataIdx, "color", true);

                if (visualColor) {
                    itemStyle.fill = visualColor;
                }
            }

            var transformPoint = function (point) {
                return [
                    point[0] * scale[0] + position[0],
                    point[1] * scale[1] + position[1],
                ];
            };

            zrUtil.each(region.geometries, function (geometry) {
                if (geometry.type !== "polygon") {
                    return;
                }

                var points = [];

                for (var i = 0; i < geometry.exterior.length; ++i) {
                    points.push(transformPoint(geometry.exterior[i]));
                }

                compoundPath.shape.paths.push(
                    new graphic.Polygon({
                        segmentIgnoreThreshold: 1,
                        shape: {
                            points: points,
                        },
                    })
                );

                for (
                    var i = 0;
                    i < (geometry.interiors ? geometry.interiors.length : 0);
                    ++i
                ) {
                    var interior = geometry.interiors[i];
                    var points = [];

                    for (var j = 0; j < interior.length; ++j) {
                        points.push(transformPoint(interior[j]));
                    }

                    compoundPath.shape.paths.push(
                        new graphic.Polygon({
                            segmentIgnoreThreshold: 1,
                            shape: {
                                points: points,
                            },
                        })
                    );
                }
            });
            compoundPath.setStyle(itemStyle);
            compoundPath.style.strokeNoScale = true;
            compoundPath.culling = true; // Label

            var showLabel = labelModel.get("show");
            var hoverShowLabel = hoverLabelModel.get("show");
            var isDataNaN =
                data && isNaN(data.get(data.mapDimension("value"), dataIdx));
            var itemLayout = data && data.getItemLayout(dataIdx); // In the following cases label will be drawn
            // 1. In map series and data value is NaN
            // 2. In geo component
            // 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout

            if (
                isGeo ||
                (isDataNaN && (showLabel || hoverShowLabel)) ||
                (itemLayout && itemLayout.showLabel)
            ) {
                var query = !isGeo ? dataIdx : region.name;
                var labelFetcher; // Consider dataIdx not found.

                if (!data || dataIdx >= 0) {
                    labelFetcher = mapOrGeoModel;
                }

                var textEl = new graphic.Text({
                    position: transformPoint(region.center.slice()),
                    // FIXME
                    // label rotation is not support yet in geo or regions of series-map
                    // that has no data. The rotation will be effected by this `scale`.
                    // So needed to change to RectText?
                    scale: [1 / group.scale[0], 1 / group.scale[1]],
                    z2: 10,
                    silent: true,
                });
                graphic.setLabelStyle(
                    textEl.style,
                    (textEl.hoverStyle = {}),
                    labelModel,
                    hoverLabelModel,
                    {
                        labelFetcher: labelFetcher,
                        labelDataIndex: query,
                        defaultText: region.name,
                        useInsideStyle: false,
                    },
                    {
                        textAlign: "center",
                        textVerticalAlign: "middle",
                    }
                );
                regionGroup.add(textEl);
            } // setItemGraphicEl, setHoverStyle after all polygons and labels
            // are added to the rigionGroup

            if (data) {
                data.setItemGraphicEl(dataIdx, regionGroup);
            } else {
                var regionModel = mapOrGeoModel.getRegionModel(region.name); // Package custom mouse event for geo component

                compoundPath.eventData = {
                    componentType: "geo",
                    componentIndex: mapOrGeoModel.componentIndex,
                    geoIndex: mapOrGeoModel.componentIndex,
                    name: region.name,
                    region: (regionModel && regionModel.option) || {},
                };
            }

            var groupRegions =
                regionGroup.__regions || (regionGroup.__regions = []);
            groupRegions.push(region);
            regionGroup.highDownSilentOnTouch =
                !!mapOrGeoModel.get("selectedMode");
            graphic.setHoverStyle(regionGroup, hoverItemStyle);
            regionsGroup.add(regionGroup);
        });

        this._updateController(mapOrGeoModel, ecModel, api);

        updateMapSelectHandler(
            this,
            mapOrGeoModel,
            regionsGroup,
            api,
            fromView
        );
        updateMapSelected(mapOrGeoModel, regionsGroup);
    },
    remove: function () {
        this._regionsGroup.removeAll();

        this._backgroundGroup.removeAll();

        this._controller.dispose();

        this._mapName &&
            geoSourceManager.removeGraphic(this._mapName, this.uid);
        this._mapName = null;
        this._controllerHost = {};
    },
    _updateBackground: function (geo) {
        var mapName = geo.map;

        if (this._mapName !== mapName) {
            zrUtil.each(
                geoSourceManager.makeGraphic(mapName, this.uid),
                function (root) {
                    this._backgroundGroup.add(root);
                },
                this
            );
        }

        this._mapName = mapName;
    },
    _updateController: function (mapOrGeoModel, ecModel, api) {
        var geo = mapOrGeoModel.coordinateSystem;
        var controller = this._controller;
        var controllerHost = this._controllerHost;
        controllerHost.zoomLimit = mapOrGeoModel.get("scaleLimit");
        controllerHost.zoom = geo.getZoom(); // roamType is will be set default true if it is null

        controller.enable(mapOrGeoModel.get("roam") || false);
        var mainType = mapOrGeoModel.mainType;

        function makeActionBase() {
            var action = {
                type: "geoRoam",
                componentType: mainType,
            };
            action[mainType + "Id"] = mapOrGeoModel.id;
            return action;
        }

        controller.off("pan").on(
            "pan",
            function (e) {
                this._mouseDownFlag = false;
                roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
                api.dispatchAction(
                    zrUtil.extend(makeActionBase(), {
                        dx: e.dx,
                        dy: e.dy,
                    })
                );
            },
            this
        );
        controller.off("zoom").on(
            "zoom",
            function (e) {
                this._mouseDownFlag = false;
                roamHelper.updateViewOnZoom(
                    controllerHost,
                    e.scale,
                    e.originX,
                    e.originY
                );
                api.dispatchAction(
                    zrUtil.extend(makeActionBase(), {
                        zoom: e.scale,
                        originX: e.originX,
                        originY: e.originY,
                    })
                );

                if (this._updateGroup) {
                    var scale = this.group.scale;

                    this._regionsGroup.traverse(function (el) {
                        if (el.type === "text") {
                            el.attr("scale", [1 / scale[0], 1 / scale[1]]);
                        }
                    });
                }
            },
            this
        );
        controller.setPointerChecker(function (e, x, y) {
            return (
                geo.getViewRectAfterRoam().contain(x, y) &&
                !onIrrelevantElement(e, api, mapOrGeoModel)
            );
        });
    },
};
var _default = MapDraw;
module.exports = _default;
